package com.onpositive.mediawiki.propertyparser;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import storage.v1.DefaultParserProvider;
import storage.v1.ParserV2;
import storage.v1.ValuesExtractorV1;

import com.onpositive.data.units.Unit;
import com.onpositive.data.units.Units;
import com.onpositive.semantic.model.api.meta.IMeta;
import com.onpositive.semantic.model.api.property.IPropertyProvider;
import com.onpositive.units.AbstractPreprocessor;
import com.onpositive.units.AbstractValueParser;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedIdent;
import com.onpositive.units.ParsedScalar;
import com.onpositive.units.ParsedSentence;
import com.onpositive.units.ParsedString;
import com.onpositive.units.ParsedValueSequenceV2;
import com.onpositive.units.ParsedValue;
import com.onpositive.units.ParsedValueSequence;
import com.onpositive.units.ValueCluster;

public abstract class AbstractExtendedParser<T extends ParsedValue> extends AbstractValueParser {

	
	
	protected static final String META_KEY_DEPTH_LEVEL = "bracket-level";
	protected static final String META_KEY_LEVEL_RESTRICTIONS = "level-restrictions" ;
	protected static final String META_KEY_PRECEEDING_COMMENT = "peceeding-comment" ;
	public AbstractExtendedParser(Class<T> targetClass, String id) {
		super(id);
		this.targetClass = targetClass;
	}

	protected static final String TAG_ABSORBED_BY = "absorbed-by";
	protected static final String TAG_IS_ALT_FOR = "is-alt-for";
	
	private static final int CUT_KIND_NEIGHBOUR_ALT_VALUE = -1;
	private static final int CUT_KIND_NON_NEIGHBOUR_ALT_VALUE = -2;
	private static final int CUT_KIND_NO_SEPARATOR = -3 ;
	private static final int CUT_KIND_DEFAULT = Integer.MIN_VALUE ;
	private static final int CUT_KIND_NEIGHBOUR_ABSORBED = -3;
	
	public static class Block<S> implements Comparable<Block<S>>
	{
		public Block(String str, int start, int end, S pVal ) {
			super();
			this.str = str;
			this.start = start;
			this.end = end;
			this.pVal = pVal ;
		}
		private final String str ;
		private final int start, end ;
		private HashMap<String,Object> meta ;
		private final S pVal ;
		
		public<VAL_TYPE> VAL_TYPE getMetaInfo( String key, Class<VAL_TYPE> valClazz ){
			
			if( meta == null )
				return null ;
			
			final Object object = meta.get(key);
			if(!valClazz.isInstance(object)) 
				return null ;
			
			return valClazz.cast(object) ;
		}
		
		public void putMetaInfo( String key, Object value ){
			
			if( meta == null )
				meta = new HashMap<String, Object>() ;
			
			meta.put(key,value) ;
		}

		public int getStart() { return start; }
		public int getEnd() { return end; }
		public S getVal() { return pVal; }

		public String getStr() {
			return str;
		}
		
		@Override
		public String toString()
		{
			StringBuilder bld = new StringBuilder() ;
			bld.append( "Block: " ) ;
			bld.append( getVal().toString() ) ;
			bld.append( " [" ) ;
			bld.append( getStart() ) ;
			bld.append( "," ) ;
			bld.append( getEnd() ) ;
			bld.append( "]" ) ;
			
			return bld.toString() ;
		}

		@Override
		public int compareTo(Block<S> o) {			
			return this.getStart()-o.getStart() ;
		}
	}
	
	Class<T> targetClass ;
	@Override
	public ParsedValue parse(String _s, IMeta meta, IPropertyProvider propertyProvider, final Map<String,Object> metaInfo,  String _comment) {
		
		String comment = getDefaultComment( _comment ) ;
		
		ArrayList<Block<T>> targetBlockList = new ArrayList<Block<T>>() ;
		String s = preprocess( _s, meta, propertyProvider, metaInfo ) ;
		if( s == null )
			s = _s ;		
		
		calculateMask( s, meta, propertyProvider, metaInfo ) ;
		
		indicateTargetValues( s, 
							  targetBlockList,
							  meta,
							  propertyProvider,
							  metaInfo,
							  comment ) ;
		
		ArrayList<ParsedValue> clustersArray = processSubstring( s, 0 , s.length(), targetBlockList, comment, metaInfo );
		
		if( clustersArray.size() == 0 ){
			//indeed? here we should start parsing recursion
			return new ParsedString(s,comment) ;
		}
		
		ParsedValue result = constructResultingValue( clustersArray, comment );		
		return result ;
	}

	protected void calculateMask(String s, IMeta meta, IPropertyProvider propertyProvider, Map<String, Object> metaInfo) {}

	private ArrayList<ParsedValue> processSubstring(
			String s,
			int startIndex,
			int endIndex,
			ArrayList<Block<T>> targetBlockList,
			String comment,
			final Map<String, Object> metaInfo)
		{
		
		ArrayList<Block<String>> bracketBlocksDivision = detectBrackets( s, startIndex, endIndex, targetBlockList ) ;
		ArrayList<ParsedValue> result = new ArrayList<ParsedValue>() ;
		
		int size = bracketBlocksDivision.size();
		for( int i = 0 ; i < size ; i++ )
		{
			Block<String> strBlock = bracketBlocksDivision.get(i) ;
			String blockKind = strBlock.getVal() ;
			
			if( blockKind == STRING_KIND_SIMPLE_STRING ){
				
				int lb = strBlock.getStart() ;
				int ub = strBlock.getEnd() ;
				if( lb == ub || s.substring(lb, ub).trim().length() == 0 )
					continue ;
				
				ArrayList<Block<T>> subList = markActualSublist(targetBlockList, lb, ub );				
				ArrayList<ParsedValue> clusterSublist =  processClearSubstring(s, lb, ub, subList, comment, metaInfo) ;
				result.addAll(clusterSublist) ;
			}
			else if( blockKind == STRING_KIND_EMBRACKETED_STRING ){
				
				int lb = strBlock.getStart() ;
				int ub = strBlock.getEnd() ;
				ArrayList<Block<T>> subList = markActualSublist(targetBlockList, lb, ub );
				increaseLevel( metaInfo ) ;
				ArrayList<ParsedValue> clusterSublist =  processSubstring(s, lb, ub, subList, comment, metaInfo) ;
				decreaseLevel( metaInfo ) ;
				
				Block<String> prevBlock = bracketBlocksDivision.get(i-1) ;
				Block<String> nextBlock = bracketBlocksDivision.get(i+1) ;				
				String localPrefix  = s.substring( prevBlock.getStart(), prevBlock.getEnd() ).trim() ;
				String localPostfix = s.substring( nextBlock.getStart(), nextBlock.getEnd() ).trim() ;
				String prefixAndPostfix[] = new String[]{ localPrefix, localPostfix } ;
				
				ParsedValueSequenceV2 pvsv2 ;
				if( clusterSublist.size() == 0 ){
					pvsv2= new ParsedValueSequenceV2(
							new ParsedValue[]{ new ParsedString( s.substring(startIndex, endIndex), comment ) },
							comment,
							prefixAndPostfix ) ;
				} else{
					ParsedValue pVal = clusterSublist.get(0) ;
					if( clusterSublist.size() == 1 && pVal instanceof ParsedValueSequenceV2 )					
						pvsv2 = (ParsedValueSequenceV2) pVal ;
				
					else
						pvsv2 = new ParsedValueSequenceV2( clusterSublist.toArray( new ParsedValue[clusterSublist.size()]),
								comment,
								prefixAndPostfix ) ;
				}
				result.add(pvsv2) ;
			}
		}
		return result;
	}
	
	static private void increaseLevel( Map<String, Object> metaInfo ) {
		
		Object levelObj = metaInfo.get( META_KEY_DEPTH_LEVEL ) ;
		if( levelObj == null || !(levelObj instanceof Integer)  )
			metaInfo.put( META_KEY_DEPTH_LEVEL, 1 ) ;			
		else{
			int level = (Integer) levelObj ;
			metaInfo.put( META_KEY_DEPTH_LEVEL, level + 1 ) ;
		}		
	}
	static private void decreaseLevel( Map<String, Object> metaInfo ) {
		
		Object levelObj = metaInfo.get( META_KEY_DEPTH_LEVEL ) ;
		if( levelObj != null && (levelObj instanceof Integer)  )
		{
			int level = (Integer) levelObj ;
			if( level <= 1 )
				metaInfo.remove( META_KEY_DEPTH_LEVEL ) ;
			else
				metaInfo.put( META_KEY_DEPTH_LEVEL, level - 1 ) ;
		}		
	}

	static private<T> boolean containsTargetBlocks( ArrayList<Block<T>> targetBlockList, int lb, int ub) {
		
		int[] bounds = marcActualBounds(targetBlockList, lb, ub);
		return bounds[0]<bounds[1] ;
	}

	static private<T> ArrayList<Block<T>> markActualSublist( ArrayList<Block<T>> targetBlockList, int lb, int ub) {
		
		int[] bounds = marcActualBounds(targetBlockList, lb, ub);
		ArrayList<Block<T>> subList = new ArrayList<Block<T>>( targetBlockList.subList(bounds[0], bounds[1]) ) ;
		return subList;
	}

	static private<T> int[] marcActualBounds(ArrayList<Block<T>> targetBlockList, int lb, int ub)
	{
		int[] bounds = new int[]{0,0} ;
		for( ; bounds[0] < targetBlockList.size() && targetBlockList.get(bounds[0]).getStart() < lb ; bounds[0]++ ) ;
		bounds[1] = bounds[0] ;
		for( ; bounds[1] < targetBlockList.size() && targetBlockList.get(bounds[1]).getEnd() <= ub ; bounds[1]++ ) ;
		return bounds;
	}

	private ArrayList<ParsedValue> processClearSubstring(
			String s,
			int lb,
			int ub,
			ArrayList<Block<T>> _subList,
			String comment,
			final Map<String, Object> metaInfo ) {
		
		ArrayList<Block<T>> subList = checkDepthLevel( _subList, metaInfo ) ;		
		
		ArrayList<ParsedValue> clustersArray = new ArrayList<ParsedValue>() ;
		ArrayList<Integer> splitBarriers = split( s, lb, ub, subList ) ;		
		ArrayList<ParsedValue> clusterBuf = new ArrayList<ParsedValue>() ;		
		//TODO comment varies, whether blocks count is 1 or more
		int targetInd = 0 ;
		boolean gotSentence = false ;
		String preceedingDelimeter = null ;
		for( int i = 0 ; i < subList.size() ; i++ )
		{
			final Block<T> curBlock = subList.get(i);
			
			int precedingPieceStart = splitBarriers.get( i*3 + 1 ) ;			
			int precedingPieceEnd   = curBlock.getStart() ;
			String precedingString = s.substring(precedingPieceStart, precedingPieceEnd).trim() ;
			
			int followingPieceStart = curBlock.getEnd() ;
			int followingPieceEnd   = splitBarriers.get( (i+1)*3 + 0 ) ;
			String followingString ="";
			try{
				followingString = s.substring( followingPieceStart, followingPieceEnd ).trim() ;
			}catch( Exception e ){
				e.printStackTrace() ;
			}
			
			String followingDelimeter = s.substring( followingPieceEnd, splitBarriers.get( (i+1)*3 + 1 ) ).trim() ;
			
			//TODO do we have a correct comment here?			
			ParsedValue precedingValue = 
					precedingString.length() != 0 ?
					parseSecondaryValue( precedingString, metaInfo, comment ) : null;
					
			ParsedValue followingValue = followingString.length() != 0 ?
					parseSecondaryValue( followingString, metaInfo, comment ) : null ;
					
			T targVal = curBlock.getVal() ;
			
			int prevCutKind = splitBarriers.get( i*3 + 2 ) ;
			int nextCutKind = splitBarriers.get( (i+1)*3 + 2 ) ;
			
			Block<T> isAltFor = curBlock.getMetaInfo(TAG_IS_ALT_FOR, Block.class) ;
			if( isAltFor != null && isAltFor.getStart() < lb )
				isAltFor = null ;
			
			Block<T> absorbedBy = curBlock.getMetaInfo(TAG_ABSORBED_BY, Block.class ) ;
			if( absorbedBy != null && absorbedBy.getStart() < lb )
				absorbedBy = null ;
			
			if( nextCutKind == CUT_KIND_NO_SEPARATOR && !alwaysCut() ){
				
				if( precedingValue != null )
					clusterBuf.add(precedingValue) ;

				String preceedingComment = curBlock.getMetaInfo(META_KEY_PRECEEDING_COMMENT, String.class) ;
				if( preceedingComment != null ){
					ParsedString ps = new ParsedString(preceedingComment, comment) ;
					clusterBuf.add(ps) ;
				}
				
				clusterBuf.add( targVal ) ;
				
				if( followingValue != null )
					clusterBuf.add(followingValue) ;
				
				gotSentence = true ;
			}			
			else if( isAltFor != null && (nextCutKind == CUT_KIND_NEIGHBOUR_ALT_VALUE || prevCutKind == CUT_KIND_NEIGHBOUR_ALT_VALUE)  )
			{
				ArrayList<ParsedValue> valArray = new ArrayList<ParsedValue>() ;
				if( precedingValue != null )
					valArray.add(precedingValue) ;
				
				String preceedingComment = curBlock.getMetaInfo(META_KEY_PRECEEDING_COMMENT, String.class) ;
				if( preceedingComment != null ){
					ParsedString ps = new ParsedString(preceedingComment, comment) ;
					clusterBuf.add(ps) ;
				}
				
				int localTargetInd = valArray.size() ;
				valArray.add( targVal ) ;
				
				if( followingValue != null )
					valArray.add(followingValue) ;
				
				String localPostDelimeter, localPrefDelimeter ;
				if( nextCutKind == CUT_KIND_NEIGHBOUR_ALT_VALUE ){
					localPostDelimeter = followingDelimeter ;
					localPrefDelimeter = null ; 
				}else{
					localPostDelimeter = null ;
					localPrefDelimeter = followingDelimeter ;
				}
				
				String[] prefixAndPostfix = new String[]{ localPrefDelimeter, localPostDelimeter } ;
				ParsedValue resultingValue = valArray.size() > 1 ?
											new ValueCluster<T>(
													valArray.toArray( new ParsedValue[valArray.size()] ),
													targetClass ,
													localTargetInd,
													comment, /*some comment*/
													prefixAndPostfix ) :
											targVal ;
				
				clusterBuf.add( resultingValue ) ;
			}
			else if( absorbedBy != null && (nextCutKind == CUT_KIND_NEIGHBOUR_ABSORBED || prevCutKind == CUT_KIND_NEIGHBOUR_ABSORBED)  )
			{
				if( precedingValue != null )
					clusterBuf.add(precedingValue) ;
				
				if( followingValue != null )
					clusterBuf.add(followingValue) ;
				
			}else{
				if( precedingValue != null )
					clusterBuf.add(precedingValue) ;
				
				String preceedingComment = curBlock.getMetaInfo(META_KEY_PRECEEDING_COMMENT, String.class) ;
				if( preceedingComment != null ){
					ParsedString ps = new ParsedString(preceedingComment, comment) ;
					clusterBuf.add(ps) ;
				}
				
				targetInd = clusterBuf.size() ;
				clusterBuf.add(targVal) ;
				
				if( followingValue != null )
					clusterBuf.add(followingValue) ;			
			}
			
			if( nextCutKind == CUT_KIND_DEFAULT || nextCutKind == CUT_KIND_NON_NEIGHBOUR_ALT_VALUE )
			{
				if( clusterBuf.size() == 1 ){
					clustersArray.add( clusterBuf.iterator().next() ) ;
					preceedingDelimeter = followingDelimeter ;
				}				
				else{
					String[] prefixAndPostfix = new String[]{ preceedingDelimeter, followingDelimeter } ;
					if ( !gotSentence ){
					ValueCluster<T> c = new ValueCluster<T>(
							clusterBuf.toArray( new ParsedValue[clusterBuf.size()] ),
							targetClass,
							targetInd,
							comment,
							prefixAndPostfix ) ;
					
					clustersArray.add(c) ;
					}
					else{
						ParsedSentence ps  = new ParsedSentence(
								clusterBuf.toArray( new ParsedValue[clusterBuf.size()] ),
								comment,
								prefixAndPostfix ) ;

						clustersArray.add(ps) ;
						gotSentence = false ;
					}
				}
				clusterBuf.clear() ;
				preceedingDelimeter = null ;
			}						
		}
		return clustersArray ;
	}
	
	
	protected boolean alwaysCut(){
		return true ;
	}
	
	
	private ArrayList<Block<T>> checkDepthLevel( ArrayList<Block<T>> _subList, Map<String, Object> metaInfo)
	{
		Object levelObj = metaInfo.get(META_KEY_DEPTH_LEVEL) ;
		if( levelObj == null || !(levelObj instanceof Integer) )
			return _subList ;
		
		int level = (Integer) levelObj ;
		if(level == 0)
			return _subList ;
		
		int prev = 0 ;
		ArrayList<Block<T>> result = new ArrayList<Block<T>>() ;
		for( int i = 0 ; i < _subList.size() ; i++ ){
			Block<T> blk = _subList.get(i) ;
			Integer levelRestrictions = blk.getMetaInfo(META_KEY_LEVEL_RESTRICTIONS, Integer.class ) ;
			if( levelRestrictions == null )
				continue ;
			
			result.addAll( _subList.subList(prev, i) ) ;
			prev = i+1 ;						
		}
		if( prev == 0 )
			return _subList;
		
		result.addAll( _subList.subList(prev, _subList.size()) ) ;
		return result ;
	}

	private final static String STRING_KIND_SIMPLE_STRING = "simple-string" ;
	private final static String STRING_KIND_EMBRACKETED_STRING = "embracketed-string" ;
	private final static String STRING_KIND_BRACKET_STRING = "bracket-string" ;
	private ArrayList<Block<String>> detectBrackets(String s, int startIndex, int endIndex, ArrayList<Block<T>> targetBlockList)
	{
		ArrayList<Block<String>> result = new ArrayList<Block<String>>() ;
		int smplStart = startIndex, embrStart = startIndex, simpleEnd = startIndex ;
		int depth = 0 ;
		for( int i = startIndex ; i < endIndex ; i++ ){
			
			char ch = s.charAt(i) ;
			
			if( ch == '(' ){
				if(depth == 0){
					embrStart = i+1;
					simpleEnd = i ;
				}
				
				depth++ ;
				continue ;
			}
			
			if( ch == ')' ){
				depth-- ;
				
				if( depth < 0 ){
					depth = 0 ;
					continue ;
				}
				
				if(depth == 0){				
					
					if( !containsTargetBlocks(targetBlockList, embrStart, i) )
						continue ;
					
					result.add( new Block<String>(s, smplStart, simpleEnd, STRING_KIND_SIMPLE_STRING) ) ;
					result.add( new Block<String>(s, simpleEnd, embrStart, STRING_KIND_BRACKET_STRING) ) ;
					result.add( new Block<String>(s, embrStart, i, STRING_KIND_EMBRACKETED_STRING) ) ;
					result.add( new Block<String>(s, i, i+1, STRING_KIND_BRACKET_STRING) ) ;
					smplStart = i+1 ;
				}				
				continue ;
			}
		}
		if( depth > 0 ){
			
			if( containsTargetBlocks(targetBlockList, embrStart, endIndex) ){
				result.add( new Block<String>(s, smplStart, embrStart-1, STRING_KIND_SIMPLE_STRING) ) ;
				result.add( new Block<String>(s, embrStart-1, embrStart, STRING_KIND_BRACKET_STRING) ) ;
				result.add( new Block<String>(s, embrStart, endIndex, STRING_KIND_EMBRACKETED_STRING) ) ;
				result.add( new Block<String>(s, endIndex, endIndex, STRING_KIND_BRACKET_STRING) ) ;
				smplStart = endIndex ;
			}			
		}		
		result.add( new Block<String>(s, smplStart, endIndex, STRING_KIND_SIMPLE_STRING) ) ;
		
		return result ;				
	}

	protected ParsedValue constructResultingValue( ArrayList<ParsedValue> clustersArray , String comment )
	{
		ParsedValue result ;
		if( clustersArray.size() == 1 )
			result =  clustersArray.get(0);
		else
			result = new ParsedValueSequenceV2( clustersArray.toArray( new ParsedValue[clustersArray.size()]), comment, null );
		
		return result ;
	}


	private ParsedValue parseSecondaryValue( String s, Map<String, Object> metaInfo, String comment)
	{
		AbstractValueParser identParser = DefaultParserProvider.getInstance().getParser( ParsedIdent.ID ) ;  
		return identParser.parse(s, null, null, metaInfo, comment) ;
//		ParsedValueSequence pvs = new ParsedValueSequence( getDefaultComment(comment) ) ; 
//		ValuesExtractorV1.processValueSubstring( pvs, s, null, null, metaInfo, comment) ;
//		
//		return pvs;
	}
	
	protected abstract void indicateTargetValues(
													final String str,
													final ArrayList<Block<T>> blockList,
													final IMeta meta,
													final IPropertyProvider propertyProvider,
													final Map<String,Object> metaInfo,
													final String comment ) ;
	
	//returns array of (cutStart,cutEnd,kind) pairs
	private final ArrayList<Integer> split(String str, int _lb, int _ub, ArrayList<Block<T>> blockList  )
	{
		ArrayList<Integer> result = new ArrayList<Integer>( ) ;
		result.add(_lb) ;
		result.add(_lb) ;
		result.add(CUT_KIND_DEFAULT) ;
		final int r0 = blockList.size()-1;
		for( int i = 0 ; i < r0 ; i++ ){
			final Block<T> curBlock = blockList.get( i );
			final Block<T> nxtBlock = blockList.get(i+1);			
			
			int lb = curBlock.getEnd() ;
			int ub = nxtBlock.getStart() ;
			int[] cutPlace = determineCutPlace( str, lb, ub ) ;
			
			int cutKind = CUT_KIND_DEFAULT ;
			if( cutPlace != null ){
				result.add( cutPlace[0] ) ;
				result.add( cutPlace[1] ) ;
			}
			else{
				result.add( ub ) ;
				result.add( ub ) ;
				cutKind = CUT_KIND_NO_SEPARATOR ;
			}
			
			Block<T> alt0 = curBlock.getMetaInfo( TAG_IS_ALT_FOR, Block.class ) ;			
			Block<T> alt1 = nxtBlock.getMetaInfo( TAG_IS_ALT_FOR, Block.class ) ;
			
			if( alt1 != null || alt0 != null )
			{
				if( curBlock == alt1 || nxtBlock == alt0 )				
					result.add( CUT_KIND_NEIGHBOUR_ALT_VALUE ) ;
				else
					result.add( CUT_KIND_NON_NEIGHBOUR_ALT_VALUE ) ;
					
				continue ;
			}
			
			Block<T> absr0 = curBlock.getMetaInfo( TAG_ABSORBED_BY, Block.class ) ;			
			Block<T> absr1 = nxtBlock.getMetaInfo( TAG_ABSORBED_BY, Block.class ) ;
			
			if( (absr1 != null && curBlock == absr1) || (absr0 != null && nxtBlock == absr0) ) {
				
				result.add( CUT_KIND_NEIGHBOUR_ABSORBED ) ;
				continue ;
			}				
							
			result.add(cutKind) ;
		}
		
		result.add( _ub ) ;
		result.add( _ub ) ;
		result.add( CUT_KIND_DEFAULT) ;
		return result;
	}
	
	private int[] determineCutPlace( String str, int lb, int ub ) {


		String[] delimeters = ParserV2.delimeters ;
		String str_lc = str.toLowerCase() ;
		int l = str_lc.length() ;
		int barrierStart = -1, barrierEnd = -1 ;
		int delimeterRank = -1 ;
		
		int bracketDepth = 0 ;
		int delimiterSearchStart = 0 ;
		for( int i = lb ; i < ub ; i++ )
		{
			if( str.startsWith("(",i ) ){
				bracketDepth++ ;
				continue ;
			}
			else if( str.startsWith(")",i ) ){
				bracketDepth-- ;
				continue ;
			}
			
			if( bracketDepth > 0 )
				continue ;
			
			//here we skip valid refs
			if( str.startsWith("[[", i ) )
			{
				int j = str.indexOf( "]]", i+2 ) ;
				if( str.lastIndexOf("[[", j ) == i )
				{
					i = j+1 ;
					continue ;
				}
			}			
			
			for( int k = delimiterSearchStart ; k < delimeters.length ; k++ )
			{
				if( str_lc.startsWith( delimeters[k], i ) )
				{
					if( !ValuesExtractorV1.confirmDelimeter( k,i,str_lc ) )
						continue ;
					
					if( i == 0 || i == l-1 || ( Character.isDigit( str_lc.charAt(i-1) )&& Character.isDigit(str_lc.charAt(i+1) ) ) )
						break ;
					
					if( k < delimeterRank )
						continue ;
					
					delimeterRank = k ;
					delimiterSearchStart = k ;
					int delimeterLength = delimeters[k].length();
					
					barrierStart = i ;
					barrierEnd = i + delimeterLength ;										
					
					i += ( delimeterLength-1 ) ; //i++ follows
				}
			}
		}
		
		return barrierStart < 0 ? null : new int[]{ barrierStart, barrierEnd} ;
	}
	
	abstract protected String preprocess(String s, IMeta meta,	IPropertyProvider propertyProvider, Map<String, Object> metaInfo);

	public static Block<Double> lookNearestNumberFromEnd(String inputString, int beginIndex, int endIndex )
	{
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;
		boolean gotPoint = false ;
		int l = str.length();
		int end = -1, start = 0 ;
		
		boolean isInsideNumber = false, hasDigit = false, gotSlash = false ;
		Double fracPart = null ;
		for( int i = l ; i > 0 ; i-- )
		{
			int curInd = i-1 ;
			char ch = str.charAt(curInd) ;
			if( Character.isDigit(ch) ){
				
				if( !isInsideNumber ){
					end = i ;
					isInsideNumber = true ;
				}
				hasDigit = true ;
				continue ;
			}				
			else if( ch == '.' && !gotPoint )
			{
				boolean pointNeedProcessing = gotDigitsAround( str, curInd) ;
				
				if( !pointNeedProcessing ){					
					if( gotDigitAbove(str, curInd) ){
						boolean positionIsValid = ( ( curInd == 0 )|| !Character.isJavaIdentifierPart( str.charAt(curInd-1) ) ) ;
					
						if(!positionIsValid){
							String preceedingString = str.substring(0, curInd);
							for( String del : ParserV2.delimeters )
							{
								//TODO validate delimeter						
								if( preceedingString.endsWith(del) ){
									positionIsValid = true ;
									break ;
								}
							}
						}
						else
							pointNeedProcessing = positionIsValid ;
						
						pointNeedProcessing = positionIsValid ;
					}
				}
				
				if( pointNeedProcessing ){
					gotPoint = true ;				
					if( !isInsideNumber ){
						start = i ;
						isInsideNumber = true ;
					}
					continue ;
				}				
				
				
			}
			else if( ch == ',' && !gotPoint )
			{
				if( gotDigitsAround( str, curInd) ){
					if( !isInsideNumber ){
						end = i ;
						isInsideNumber = true ;
					}
					continue ;
				}				
			}else if( ch == '/' && !gotPoint && isInsideNumber ){
				if( gotDigitsAround( str, curInd) ){
					gotPoint = true ;
					gotSlash = true ;
					continue ;
				}
				else if( !hasDigit ){
					isInsideNumber = false ;
					gotPoint = false ;
					continue ;
				}
			}
			else if( ch == ' ' ){
				
				if( !hasDigit )
					isInsideNumber = false ;
				
				if( !isInsideNumber )
					gotPoint = false ;
				
				continue ;
			}
			
			if( !hasDigit && fracPart == null ){
				fracPart = fracSet.get(ch) ;
				if( fracPart != null ){
					isInsideNumber = true ;
					hasDigit = true ;
					end = curInd+1 ;
					continue ;
				}
			}
			
			if( hasDigit )
			{				
				start = i ;
				break ;
			}
			else{
				isInsideNumber = false ;
				gotPoint = false ;
			}
		}
		if(!hasDigit)
			return null ;
		
		for( ;Character.isWhitespace( str.charAt(start)); start++ ) ;
		
		try{
			double doubleValue = 0.0 ;
			boolean gotValue = false ;
			if( fracPart == null ){
				int mainPartEnd = end ;
				if( gotSlash ){
					int slashInd = str.indexOf('/',start) ;
					int i0 = slashInd ;
					int i1 = slashInd+1 ;
					for( ; i0 > 0 && Character.isDigit( str.charAt(i0-1)) ; i0-- ) ;
					for( ; i1 < l && Character.isDigit( str.charAt( i1 )) ; i1++ ) ;
					String   numeratorString = str.substring( i0 , slashInd) ;
					String denominatorString = str.substring(slashInd+1, i1) ;					
					if( numeratorString.length() != 0 || denominatorString.length() != 0 ){
						try{
							double d0 = NumberFormat.getInstance(Locale.US).parse(  numeratorString).doubleValue();
							double d1 = NumberFormat.getInstance(Locale.US).parse(denominatorString).doubleValue();
							if( d1 > epsilon ){
								double d = d0/d1 ;
								doubleValue += d ;
							}
						}catch(ParseException e){
							e.printStackTrace() ;
						}
					}
					mainPartEnd = i0 ;
					gotValue = true ;
				}
				final String valStr = str.substring(start, mainPartEnd).trim() ;
				if( valStr.length() != 0 ){
					doubleValue += NumberFormat.getInstance(Locale.US).parse(valStr).doubleValue();
					gotValue = true ;
				}
				if( !gotValue )
					return null ;
			}
			else{
				String valStr = str.substring(start, end-1).trim() ;
				doubleValue = NumberFormat.getInstance(Locale.US).parse(valStr).doubleValue();
				doubleValue += fracPart ;
			}
			Block<Double> result = new Block<Double>(str, start + beginIndex, end + beginIndex, doubleValue );
			String substring = inputString.substring(result.getStart(), result.getEnd()) ;
			String trim = substring.trim() ;
			if( !substring.equals(trim))
				return result ;
			else
				return result ;
						
		}catch( ParseException e ){
			System.err.println( e.getClass().getCanonicalName() );
			StackTraceElement[] clone = e.getStackTrace().clone();
			if(clone.length>1){
				System.err.println( clone[1].toString() );
			}
			System.err.println( str );
			System.err.println( beginIndex + ", " + endIndex );			
		}
		return null ;
	}

	private static double epsilon = Double.MIN_VALUE * 1000000000 ;
	public static Block<Double> lookNearestNumberFromStart( String inputString, int beginIndex, int endIndex ){
		
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;		
		boolean gotPoint = false ;
		int l = str.length();
		int end = l, start = -1 ;
		
		Double fracPart = null ;
		boolean isInsideNumber = false, hasDigit = false, gotSlash = false ;
		for( int i = 0 ; i < l ; i++ )
		{
			int curInd = i ;
			char ch = str.charAt(curInd) ;
			if( Character.isDigit(ch) ){
				
				if( !isInsideNumber ){
					start = i ;
					isInsideNumber = true ;
				}
				hasDigit = true ;
				continue ;
			}				
			else if( ch == '.' && !gotPoint )
			{
				boolean pointNeedProcessing = gotDigitsAround( str, curInd) ;
				
				if( !pointNeedProcessing ){					
					if( gotDigitAbove(str, curInd) ){
						boolean positionIsValid = ( ( curInd == 0 )|| !Character.isJavaIdentifierPart( str.charAt(curInd-1) ) ) ;
					
						if(!positionIsValid){
							String preceedingString = str.substring(0, curInd);
							for( String del : ParserV2.delimeters )
							{
								//TODO validate delimeter						
								if( preceedingString.endsWith(del) ){
									positionIsValid = true ;
									break ;
								}
							}
						}
						else
							pointNeedProcessing = positionIsValid ;
						
						pointNeedProcessing = positionIsValid ;
					}
				}
				
				if( pointNeedProcessing ){
					gotPoint = true ;				
					if( !isInsideNumber ){
						start = i ;
						isInsideNumber = true ;
					}					
				}				
				
				continue ;
			}
			else if( ch == ',' && !gotPoint )
			{				
				if( gotDigitsAround( str, curInd) ){
					if( !isInsideNumber ){
						start = i ;
						isInsideNumber = true ;
					}
					continue ;
				}
			}else if( ch == '/' && !gotPoint && isInsideNumber ){				
				if( gotDigitsAround( str, curInd) ){
					gotPoint = true ;
					gotSlash = true ;
					continue ;
				}
				else if( !hasDigit ){
					isInsideNumber = false ;
					gotPoint = false ;
					continue ;
				}
			}
			else if( ch == ' ' ){
				if( !hasDigit )
					isInsideNumber = false ;
				
				if( !isInsideNumber )
					gotPoint = false ;
				
				continue ;
			}
			
			fracPart = fracSet.get(ch) ;
			if( fracPart != null ){
				if( !isInsideNumber )
					start = i ;
					
				end = curInd+1 ;
				break ;
			}
			
			if( hasDigit )
			{				
				end = i ;
				break ;
			}
			else{
				isInsideNumber = false ;
				gotPoint = false ;
			}
		}
		if(!hasDigit)
			return null ;		
		
		for( ;Character.isWhitespace( str.charAt(end-1)); end-- ) ;
		
		try{
			double doubleValue = 0.0 ;
			boolean gotValue = false ;
			if( fracPart == null ){
				int mainPartEnd = end ;
				if( gotSlash ){
					int slashInd = str.indexOf('/',start) ;
					int i0 = slashInd ;
					int i1 = slashInd+1 ;
					for( ; i0 > 0 && Character.isDigit( str.charAt(i0-1)) ; i0-- ) ;
					for( ; i1 < l && Character.isDigit( str.charAt( i1 )) ; i1++ ) ;
					String   numeratorString = str.substring( i0 , slashInd).trim().replace(' ', ',') ;
					String denominatorString = str.substring(slashInd+1, i1).trim().replace(' ', ',') ;
					if( numeratorString.length() != 0 || denominatorString.length() != 0 ){
						try{
							double d0 = NumberFormat.getInstance(Locale.US).parse(  numeratorString).doubleValue();
							double d1 = NumberFormat.getInstance(Locale.US).parse(denominatorString).doubleValue();
							if( d1 > epsilon ){
								double d = d0/d1 ;
								doubleValue += d ;
							}
						}catch(ParseException e){
							System.err.println( e.getClass().getCanonicalName());
							StackTraceElement[] clone = e.getStackTrace().clone();
							if(clone.length!=1){
								System.err.println( clone[1].toString() );
							}
							System.err.println( str );
							System.err.println( beginIndex );
						}
					}
					mainPartEnd = i0 ;
					gotValue = true ;
				}
				String valStr = str.substring(start, mainPartEnd).trim().replace(' ', ',') ;
				if( valStr.length() != 0 ){
					doubleValue += NumberFormat.getInstance(Locale.US).parse(valStr).doubleValue();
					gotValue = true ;
				}
				if( !gotValue )
					return null ;
			}
			else{
				String valStr = str.substring(start, end-1).trim().replace(' ', ',') ;
				doubleValue = NumberFormat.getInstance(Locale.US).parse(valStr).doubleValue();
				doubleValue += fracPart ;
			}
			Block<Double> result = new Block<Double>(str, start + beginIndex,end + beginIndex, doubleValue );
			String substring = inputString.substring(result.getStart(), result.getEnd()) ;
			String trim = substring.trim() ;
			if( !substring.equals(trim))
				return result ;
			else
				return result ;									
						
		}catch( ParseException e ){
			e.printStackTrace() ;
			System.out.println( str );
			System.out.println( beginIndex + ", " + endIndex );
		}
		return null ;
	}

	private static boolean gotDigitsAround(String str, int ind )
	{		
		return gotDigitBelow(str, ind) && gotDigitAbove(str, ind);
	}

	private static boolean gotDigitAbove(String str, int ind)
	{
		int l = str.length() ;
		if( ind < 0 || ind > l-2 )
			return false ;
		
		int post = ind+1 ;
		for(  ; post < l && str.charAt(post) == ' ' ; post++ ) ;
		if( post == l || !Character.isDigit(str.charAt(post)) )
			return false ;

		return true;
	}

	private static boolean gotDigitBelow(String str, int ind)
	{
		int l = str.length() ;
		if( ind < 1 || ind > l-1 )
			return false ;
		
		int prev = ind-1 ;
		for(  ; prev >= 0 && str.charAt(prev) == ' ' ; prev-- ) ;
		if( prev < 0 || !Character.isDigit(str.charAt(prev)) )
			return false ;
		
		return true ;
	}
	
	static protected<T> boolean intersects( Block pcBlock, Collection<Block<T>> blockList)
	{
		if( blockList.isEmpty() )
			return false ;
		
		int blockStart = pcBlock.getStart() ;
		int blockEnd   = pcBlock.getEnd()   ;
		for( Block<?> blk : blockList ){
			int bs = blk.getStart() ;
			int be = blk.getEnd() ;
			
			if( bs >= blockStart && bs < blockEnd )
				return true ;
			
			if( be >= blockStart && be < blockEnd )
				return true ;			
		}
		return false;
	}

	public static Block<Unit> lookUnitInTheEnd( String inputString, int beginIndex, int endIndex, String unitKind ){
		
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;
		int offset = 0, l0 = str.length();
		for( ; offset < l0 && Character.isWhitespace( str.charAt(offset) ) ; offset++ ) ;
		str= str.substring(offset) ;

		ArrayList<Integer> barriers = indicateBarriers(str) ;
		Unit u = null, unit = null ;	
		
		int ind0 = -1, ind1 = barriers.get( barriers.size()-1 ), start = -1, end = -1 ;
		for ( int i = barriers.size()-1 ; i > 0 ; i-- ) {
			
			ind0 = barriers.get(i-1) ;			
			
			String unitName = str.substring(ind0+1,ind1).replaceAll(" +", " ").trim() ;
			if ( !Units.isUnit(unitName) )
				continue ;
			
			u = Units.getUnitByShortName( unitName, unitKind );				
			if( u == null || u.equals(unit) )
				continue ;
				
			start = ind0 + 1 ;
			end = start + unitName.length() ;
			unit = u ;				
		}		
		if( unit == null )
			return null ;
		
		int blockStart = beginIndex + offset + start ;		
		int blockEnd =   beginIndex + offset + end   ;		
		Block<Unit> result = new Block<Unit>( str, blockStart, blockEnd, unit );
		String substring = inputString.substring(result.getStart(), result.getEnd()) ;
		String trim = substring.trim() ;
		if( !substring.equals(trim))
			return result ;
		else
			return result ;	
	}
	
	
	public static  Block<Unit> lookUnitInTheBeginning( String inputString, int beginIndex, int endIndex, String unitKind ){
		
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;
		int offset = 0, l0 = str.length();
		for( ; offset < l0 && Character.isWhitespace( str.charAt(offset) ) ; offset++ ) ;
		str= str.substring(offset) ;
		
		ArrayList<Integer> barriers = indicateBarriers(str) ;
		Unit u = null, unit = null ;	
		
		int ind0 = barriers.get( 0 ), ind1 = -1, start = -1, end = -1 ;
		int size = barriers.size();
		for ( int i = 1 ; i < size ; i++ ) {
			
			ind1 = barriers.get(i) ;			
			
			String unitName = str.substring(ind0+1,ind1).replaceAll(" +", " ").trim() ;
			if ( !Units.isUnit(unitName) )
				continue ;
			
			u = Units.getUnitByShortName( unitName, unitKind );				
			if( u == null || u.equals(unit) )
				continue ;
			
			end = ind1 ;
			start = ind1 - unitName.length() ;
			
			unit = u ;				
		}		
		if( unit == null )
			return null ;
		
		int blockStart = beginIndex + offset + start ;		
		int blockEnd =   beginIndex + offset + end   ;		
		Block<Unit> result = new Block<Unit>( str, blockStart, blockEnd, unit );
		String substring = inputString.substring(result.getStart(), result.getEnd()) ;
		String trim = substring.trim() ;
		if( !substring.equals(trim))
			return result ;
		else
			return result ;	
	}
	
	private final static char[] barriers = new char[]{' ', ',', ';', '.', '(', ')', ']', '[', '/'} ;
	private static ArrayList<Integer> indicateBarriers(String str) {
		
		ArrayList<Integer>  result = new ArrayList<Integer>() ;
		result.add(-1) ;
		int l = str.length() ;
lb0:	for( int i = 0 ; i < l ; i++ )
		{
			char ch = str.charAt(i) ;
			for( char del : barriers )
			{
				if( ch == del ){
					result.add(i) ;					
					continue lb0;
				}
			}
		}
		result.add(l) ;

		return result;
	}
	
	static private HashMap<Character,Double> fracSet = new HashMap<Character,Double>();
	{
		
		
		fracSet.put('\u00BC', 0.25  );//	1/4
		fracSet.put('\u00BD', 0.5 ) ;
		fracSet.put('\u00BE', 0.75  );//	3/4
		
		fracSet.put('\u2150', 0.142857 );//(142857)	1/7		
		fracSet.put('\u2151', 0.111111   );//(1)	1/9		
		fracSet.put('\u2152', 0.1   );//	1/10
		
		fracSet.put('\u2153', 0.333333   );//(3)	1/3
		fracSet.put('\u2154', 0.666666   );//(6)	2/3
		
		fracSet.put('\u2155', 0.2   );//	1/5
		fracSet.put('\u2156', 0.4   );//	2/5
		fracSet.put('\u2157', 0.6   );//	3/5
		fracSet.put('\u2158', 0.8   );//	4/5
				
		fracSet.put('\u2159', 0.166666 );//(6)	1/6
		fracSet.put('\u215A', 0.833333 );//(3)	5/6
		
		fracSet.put('\u215B', 0.125 );//	1/8
		fracSet.put('\u215C', 0.375 );//	3/8		
		fracSet.put('\u215D', 0.625 );//	5/8		
		fracSet.put('\u215E', 0.875 );//	7/8		
		
		fracSet.put('\u2189', 0.0   );//	0/3 baseball stuff
			
	}
	
}
